Introduction

Lily Moreno, Director of Marketing for the Chicago area’s Cyclistic bike-share program, understood the company’s future success depended on a focused approach to marketing that maximized the number of annual memberships. I am a junior data analyst working with the marketing analyst team at Cyclistic. Ms Moreno needs analytics to inform her design marketing strategies that would help Cyclistic maximized the number of annual memberships.tactics. Moreno and her team are interested in analyzing the Cyclistic historical bike trip data to identify trends. Your team is tasked to present data with simple and compelling visualizations that will communicate insights for Cyclistic executives’decision making process.

This analysis is for case study 1 from the Google Data Analytics Certificate (Cyclistic). It’s originally based on the case study “‘Sophisticated, Clear, and Polished’: Divvy and Data Visualization” written by Kevin Hartman (found here: https://artscience.blog/home/divvy-dataviz-case-study).

We will follow the data analysis process: ask, prepare, process, analyze,share, and act to come up with a solution or make reccomendations for the business task

Business Task: How can Cyclistic maximize the number of annual memberships?

STEP 1: COLLECT DATA

We begin the analysis by loading the R packages needed for this exercise

install.packages("tidyverse")
install.packages("lubridate")
install.packages("dplyr")
install.packages("ggplot2")
install.packages("ggthemes")
library(tidyverse)
library(lubridate)
library(dplyr)
library(ggplot2)
library(ggthemes)
getwd()
setwd("/Users/tushy/OneDrive/Desktop/Capstone Google/Capstone")

STEP 1: COLLECT DATA

Loading data (12 months Starting October 2020 to end of September 2021 for this analysis)

biketripdata_2020_10 <- read.csv("202010-divvy-tripdata.csv")
biketripdata_2020_11 <- read.csv("202011-divvy-tripdata.csv")
biketripdata_2020_12 <- read.csv("202012-divvy-tripdata.csv")
biketripdata_2021_01 <- read.csv("202101-divvy-tripdata.csv")
biketripdata_2021_02 <- read.csv("202102-divvy-tripdata.csv")
biketripdata_2021_03 <- read.csv("202103-divvy-tripdata.csv")
biketripdata_2021_04 <- read.csv("202104-divvy-tripdata.csv")
biketripdata_2021_05 <- read.csv("202105-divvy-tripdata.csv")
biketripdata_2021_06 <- read.csv("202106-divvy-tripdata.csv")
biketripdata_2021_07 <- read.csv("202107-divvy-tripdata.csv")
biketripdata_2021_08 <- read.csv("202108-divvy-tripdata.csv")
biketripdata_2021_09 <- read.csv("202109-divvy-tripdata.csv")

STEP 2: WRANGLE DATA AND COMBINE INTO A SINGLE FILE

Looking at data details more closely using str()

We need the all data to stack correctly when we combine the all the data for analysis. Some of the start_station_id’s and end_station_id’s need to be converted into characters format for this process.

biketripdata_2020_10 <- mutate(biketripdata_2020_10, start_station_id = as.character(start_station_id), end_station_id = as.character(end_station_id))
biketripdata_2020_11 <- mutate(biketripdata_2020_11, start_station_id = as.character(start_station_id), end_station_id = as.character(end_station_id))

After the data is stacked correctly we bind all of our csv spreadsheets for analysis

tot_biketrips <-bind_rows(
biketripdata_2020_10,
biketripdata_2020_11,
biketripdata_2020_12,
biketripdata_2021_01,
biketripdata_2021_02,
biketripdata_2021_03,
biketripdata_2021_04,
biketripdata_2021_05,
biketripdata_2021_06,
biketripdata_2021_07,
biketripdata_2021_08,
biketripdata_2021_09)

Inspect data to view changes and Clean before analysis using these functions: colnames()/summary()/head()

colnames(tot_biketrips)
 [1] "ride_id"            "rideable_type"      "started_at"         "ended_at"          
 [5] "start_station_name" "start_station_id"   "end_station_name"   "end_station_id"    
 [9] "start_lat"          "start_lng"          "end_lat"            "end_lng"           
[13] "member_casual"     
nrow(tot_biketrips)
[1] 5136261
summary(tot_biketrips)
   ride_id          rideable_type       started_at          ended_at         start_station_name
 Length:5136261     Length:5136261     Length:5136261     Length:5136261     Length:5136261    
 Class :character   Class :character   Class :character   Class :character   Class :character  
 Mode  :character   Mode  :character   Mode  :character   Mode  :character   Mode  :character  
                                                                                               
                                                                                               
                                                                                               
                                                                                               
 start_station_id   end_station_name   end_station_id       start_lat       start_lng     
 Length:5136261     Length:5136261     Length:5136261     Min.   :41.64   Min.   :-87.84  
 Class :character   Class :character   Class :character   1st Qu.:41.88   1st Qu.:-87.66  
 Mode  :character   Mode  :character   Mode  :character   Median :41.90   Median :-87.64  
                                                          Mean   :41.90   Mean   :-87.65  
                                                          3rd Qu.:41.93   3rd Qu.:-87.63  
                                                          Max.   :42.08   Max.   :-87.52  
                                                                                          
    end_lat         end_lng       member_casual     
 Min.   :41.51   Min.   :-88.07   Length:5136261    
 1st Qu.:41.88   1st Qu.:-87.66   Class :character  
 Median :41.90   Median :-87.64   Mode  :character  
 Mean   :41.90   Mean   :-87.65                     
 3rd Qu.:41.93   3rd Qu.:-87.63                     
 Max.   :42.17   Max.   :-87.44                     
 NA's   :4821    NA's   :4821                       
colnames(tot_biketrips)
 [1] "ride_id"            "rideable_type"      "started_at"         "ended_at"          
 [5] "start_station_name" "start_station_id"   "end_station_name"   "end_station_id"    
 [9] "start_lat"          "start_lng"          "end_lat"            "end_lng"           
[13] "member_casual"     
nrow(tot_biketrips)
[1] 5136261
summary(tot_biketrips)
   ride_id          rideable_type       started_at          ended_at         start_station_name
 Length:5136261     Length:5136261     Length:5136261     Length:5136261     Length:5136261    
 Class :character   Class :character   Class :character   Class :character   Class :character  
 Mode  :character   Mode  :character   Mode  :character   Mode  :character   Mode  :character  
                                                                                               
                                                                                               
                                                                                               
                                                                                               
 start_station_id   end_station_name   end_station_id       start_lat       start_lng     
 Length:5136261     Length:5136261     Length:5136261     Min.   :41.64   Min.   :-87.84  
 Class :character   Class :character   Class :character   1st Qu.:41.88   1st Qu.:-87.66  
 Mode  :character   Mode  :character   Mode  :character   Median :41.90   Median :-87.64  
                                                          Mean   :41.90   Mean   :-87.65  
                                                          3rd Qu.:41.93   3rd Qu.:-87.63  
                                                          Max.   :42.08   Max.   :-87.52  
                                                                                          
    end_lat         end_lng       member_casual     
 Min.   :41.51   Min.   :-88.07   Length:5136261    
 1st Qu.:41.88   1st Qu.:-87.66   Class :character  
 Median :41.90   Median :-87.64   Mode  :character  
 Mean   :41.90   Mean   :-87.65                     
 3rd Qu.:41.93   3rd Qu.:-87.63                     
 Max.   :42.17   Max.   :-87.44                     
 NA's   :4821    NA's   :4821                       
head(tot_biketrips)
ABCDEFGHIJ0123456789
 
 
ride_id
<chr>
rideable_type
<chr>
started_at
<chr>
ended_at
<chr>
1ACB6B40CF5B9044Celectric_bike2020-10-31 19:39:432020-10-31 19:57:12
2DF450C72FD109C01electric_bike2020-10-31 23:50:082020-11-01 00:04:16
3B6396B54A15AC0DFelectric_bike2020-10-31 23:00:012020-10-31 23:08:22
444A4AEE261B9E854electric_bike2020-10-31 22:16:432020-10-31 22:19:35
510B7DD76A6A2EB95electric_bike2020-10-31 19:38:192020-10-31 19:54:32
6DA6C3759660133DAelectric_bike2020-10-29 17:38:042020-10-29 17:45:43

STEP 3: CLEAN UP AND ADD DATA TO PREPARE FOR ANALYSIS

In order to do further cleaning and calculations, we must add dates of columns to the cleaned data set such as day, week, month and year.

tot_biketrips_clean$date <- as.Date(tot_biketrips_clean$started_at)
tot_biketrips_clean$month <- format(as.Date(tot_biketrips_clean$date), "%m")
tot_biketrips_clean$day <- format(as.Date(tot_biketrips_clean$date), "%d")
tot_biketrips_clean$year <- format(as.Date(tot_biketrips_clean$date), "%Y")
tot_biketrips_clean$day_of_week <- format(as.Date(tot_biketrips_clean$date), "%A")

check out rows to see if the date columns were added

glimpse(tot_biketrips_clean)
Rows: 5,045,921
Columns: 18
$ ride_id            <chr> "ACB6B40CF5B9044C", "DF450C72FD109C01", "B6396B54A15AC0DF", "44A4AEE261~
$ rideable_type      <chr> "electric_bike", "electric_bike", "electric_bike", "electric_bike", "el~
$ started_at         <chr> "2020-10-31 19:39:43", "2020-10-31 23:50:08", "2020-10-31 23:00:01", "2~
$ ended_at           <chr> "2020-10-31 19:57:12", "2020-11-01 00:04:16", "2020-10-31 23:08:22", "2~
$ start_station_name <chr> "Lakeview Ave & Fullerton Pkwy", "Southport Ave & Waveland Ave", "Stony~
$ start_station_id   <chr> "313", "227", "102", "165", "190", "359", "313", "125", "174", "114", "~
$ end_station_name   <chr> "Rush St & Hubbard St", "Kedzie Ave & Milwaukee Ave", "University Ave &~
$ end_station_id     <chr> "125", "260", "423", "256", "185", "53", "125", "313", "635", "303", "1~
$ start_lat          <dbl> 41.92610, 41.94817, 41.77346, 41.95085, 41.92886, 41.90353, 41.92584, 4~
$ start_lng          <dbl> -87.63898, -87.66391, -87.58537, -87.65924, -87.66396, -87.64335, -87.6~
$ end_lat            <dbl> 41.89035, 41.92953, 41.79145, 41.95281, 41.91778, 41.89440, 41.89047, 4~
$ end_lng            <dbl> -87.62607, -87.70782, -87.60005, -87.65010, -87.69143, -87.63431, -87.6~
$ member_casual      <chr> "casual", "casual", "casual", "casual", "casual", "casual", "casual", "~
$ date               <date> 2020-10-31, 2020-10-31, 2020-10-31, 2020-10-31, 2020-10-31, 2020-10-29~
$ month              <chr> "10", "10", "10", "10", "10", "10", "10", "10", "10", "10", "10", "10",~
$ day                <chr> "31", "31", "31", "31", "31", "29", "29", "29", "29", "28", "29", "29",~
$ year               <chr> "2020", "2020", "2020", "2020", "2020", "2020", "2020", "2020", "2020",~
$ day_of_week        <chr> "Saturday", "Saturday", "Saturday", "Saturday", "Saturday", "Thursday",~

Add a “ride_length” calculation to tot_biketrips cleaned (in seconds)

tot_biketrips_clean$ride_length <- difftime(tot_biketrips_clean$ended_at,tot_biketrips_clean$started_at)

###3 Checking data type for consistency

Remove “bad” data for bikes taken out of circulation in order to omit negative values in ride length

tot_biketrips_v2 <- tot_biketrips_clean[!(tot_biketrips_clean$start_station_name == "HQ QR" | tot_biketrips_clean$ride_length<0),]

STEP 4: CONDUCT DESCRIPTIVE ANALYSIS

min(tot_biketrips_v2$ride_length) # shortest ride
[1] 0
max(tot_biketrips_v2$ride_length) # longest ride
[1] 3356649
mean(tot_biketrips_v2$ride_length) # average (total ride length / rides)
[1] 1326.167
median(tot_biketrips_v2$ride_length) # midpoint
[1] 757
summary(tot_biketrips_v2)
   ride_id          rideable_type       started_at          ended_at         start_station_name
 Length:5042638     Length:5042638     Length:5042638     Length:5042638     Length:5042638    
 Class :character   Class :character   Class :character   Class :character   Class :character  
 Mode  :character   Mode  :character   Mode  :character   Mode  :character   Mode  :character  
                                                                                               
                                                                                               
                                                                                               
 start_station_id   end_station_name   end_station_id       start_lat       start_lng     
 Length:5042638     Length:5042638     Length:5042638     Min.   :41.64   Min.   :-87.84  
 Class :character   Class :character   Class :character   1st Qu.:41.88   1st Qu.:-87.66  
 Mode  :character   Mode  :character   Mode  :character   Median :41.90   Median :-87.64  
                                                          Mean   :41.90   Mean   :-87.65  
                                                          3rd Qu.:41.93   3rd Qu.:-87.63  
                                                          Max.   :42.07   Max.   :-87.52  
    end_lat         end_lng       member_casual           date               month          
 Min.   :41.51   Min.   :-88.07   Length:5042638     Min.   :2020-10-01   Length:5042638    
 1st Qu.:41.88   1st Qu.:-87.66   Class :character   1st Qu.:2021-04-18   Class :character  
 Median :41.90   Median :-87.64   Mode  :character   Median :2021-06-23   Mode  :character  
 Mean   :41.90   Mean   :-87.65                      Mean   :2021-05-29                     
 3rd Qu.:41.93   3rd Qu.:-87.63                      3rd Qu.:2021-08-12                     
 Max.   :42.17   Max.   :-87.49                      Max.   :2021-09-30                     
     day                year           day_of_week         ride_length     
 Length:5042638     Length:5042638     Length:5042638     Min.   :      0  
 Class :character   Class :character   Class :character   1st Qu.:    427  
 Mode  :character   Mode  :character   Mode  :character   Median :    757  
                                                          Mean   :   1326  
                                                          3rd Qu.:   1369  
                                                          Max.   :3356649  

Compare members and casual users

aggregate(tot_biketrips_v2$ride_length ~ tot_biketrips_v2$member_casual, FUN = mean)
aggregate(tot_biketrips_v2$ride_length ~ tot_biketrips_v2$member_casual, FUN = median)
aggregate(tot_biketrips_v2$ride_length ~ tot_biketrips_v2$member_casual, FUN = max)
aggregate(tot_biketrips_v2$ride_length ~ tot_biketrips_v2$member_casual, FUN = min)

See the average ride time by each day for members vs casual users

aggregate(tot_biketrips_v2$ride_length ~ tot_biketrips_v2$member_casual + tot_biketrips_v2$day_of_week, FUN = mean)
ABCDEFGHIJ0123456789
tot_biketrips_v2$member_casual
<chr>
tot_biketrips_v2$day_of_week
<chr>
tot_biketrips_v2$ride_length
<dbl>
casualFriday1819.9010
memberFriday823.9347
casualMonday1885.4267
memberMonday803.7666
casualSaturday2067.1800
memberSaturday926.4486
casualSunday2213.0851
memberSunday943.8355
casualThursday1633.4148
memberThursday783.7299

Organize the week days

tot_biketrips_v2$day_of_week <- ordered(tot_biketrips_v2$day_of_week, levels=c("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"))

Detemine the average ride time by each day for members vs casual users

aggregate(tot_biketrips_v2$ride_length ~ tot_biketrips_v2$member_casual + tot_biketrips_v2$day_of_week, FUN = mean)
ABCDEFGHIJ0123456789
tot_biketrips_v2$member_casual
<chr>
tot_biketrips_v2$day_of_week
<ord>
tot_biketrips_v2$ride_length
<dbl>
casualSunday2213.0851
memberSunday943.8355
casualMonday1885.4267
memberMonday803.7666
casualTuesday1702.6415
memberTuesday786.1274
casualWednesday1657.3003
memberWednesday790.6077
casualThursday1633.4148
memberThursday783.7299

Analyze ridership data by type and weekday

`

tot_biketrips_v2 %>%
mutate(weekday = wday(started_at, label = TRUE)) %>%  #creates weekday field using wday()
group_by(member_casual, weekday) %>%  #groups by usertype and weekday
summarise(number_of_rides = n()                         #calculates the number of rides and average duration
,average_duration = mean(ride_length)) %>%      # calculates the average duration
arrange(member_casual, weekday)                             # sorts
`summarise()` has grouped output by 'member_casual'. You can override using the `.groups` argument.
ABCDEFGHIJ0123456789
member_casual
<chr>
weekday
<ord>
number_of_rides
<int>
average_duration
<dbl>
casualSun4389552213.0851
casualMon2622431885.4267
casualTue2472121702.6415
casualWed2526391657.3003
casualThu2690591633.4148
casualFri3326371819.9010
casualSat5146552067.1800
memberSun338824943.8355
memberMon369040803.7666
memberTue400265786.1274
View(tot_biketrips_v2)

STEP 5: VISUALIZATION SECTION

Visualize the number of rides by rider type

tot_biketrips_v2 %>% 
  mutate(weekday = wday(started_at, label = TRUE)) %>% 
  group_by(member_casual, weekday) %>% 
  summarise(number_of_rides = n()
            ,average_duration = mean(ride_length)) %>% 
  arrange(member_casual, weekday)  %>% 
  ggplot(aes(x = weekday, y = number_of_rides, fill = member_casual, caption = "Data by Motivate International Inc")) +
  geom_col(position = "dodge")+
  theme_bw()
`summarise()` has grouped output by 'member_casual'. You can override using the `.groups` argument.

NA
NA

Visualization for average duration of rides

`summarise()` has grouped output by 'member_casual'. You can override using the `.groups` argument.

Matching rider with type of bike

  type_of_bike <- tot_biketrips_clean %>% filter(rideable_type=="classic_bike" | rideable_type=="electric_bike")

checking the bike type usage by user type

  type_of_bike %>%
group_by(member_casual,rideable_type) %>%
    summarise(totals=n(), .groups="drop")  %>%

ggplot()+
    geom_col(aes(x=member_casual,y=totals,fill=rideable_type), position = "dodge") + 
    labs(title = "Type of bike usage by Rider type",x="Rider type",y=NULL, fill="Bike type") +
    scale_fill_manual(values = c("classic_bike" = "#7291C9","electric_bike" = "#79CC85")) +
    theme_bw() +
    theme(legend.position="top")

Bike usage by both user types during a week

  type_of_bike %>%
    mutate(weekday = wday(started_at, label = TRUE)) %>% 
    group_by(member_casual,rideable_type,weekday) %>%
    summarise(totals=n(), .groups="drop") %>%

ggplot(aes(x=weekday,y=totals, fill=rideable_type)) +
  geom_col(position = "dodge") + 
  facet_wrap(~member_casual) +
  labs(title = "Bike type usage by user type during a week",x="User type",y=NULL,caption = "Data by Motivate International Inc") +
  scale_fill_manual(values = c("classic_bike" = "#7291C9","electric_bike" = "#79CC85")) +
  theme_bw() +
  theme(legend.position="top")

NA
NA
NA
LS0tDQp0aXRsZTogIkdvb2dsZSBEYXRhIEFuYWx5dGljcyBDZXJ0aWZpY2F0ZSAoQ3ljbGlzdGljKSINCnN1YnRpdGxlOiAiQ2Fwc3RvbmUgUHJvamVjdCINCkJ5OiAiQ2hhcm1haW4gS3JvbWFoIFBhdGVsIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCiMjIEludHJvZHVjdGlvbg0KIyMjIyAgTGlseSBNb3Jlbm8sIERpcmVjdG9yIG9mIE1hcmtldGluZyBmb3IgdGhlIENoaWNhZ28gYXJlYeKAmXMgQ3ljbGlzdGljIGJpa2Utc2hhcmUgcHJvZ3JhbSwgdW5kZXJzdG9vZCB0aGUgY29tcGFueeKAmXMgZnV0dXJlIHN1Y2Nlc3MgZGVwZW5kZWQgb24gYSBmb2N1c2VkIGFwcHJvYWNoIHRvIG1hcmtldGluZyB0aGF0IG1heGltaXplZCB0aGUgbnVtYmVyIG9mIGFubnVhbCBtZW1iZXJzaGlwcy4gSSBhbSBhICBqdW5pb3IgZGF0YSBhbmFseXN0IHdvcmtpbmcgd2l0aCB0aGUgbWFya2V0aW5nIGFuYWx5c3QgdGVhbSBhdCBDeWNsaXN0aWMuIE1zIE1vcmVubyBuZWVkcyBhbmFseXRpY3MgdG8gaW5mb3JtICBoZXIgZGVzaWduIG1hcmtldGluZyBzdHJhdGVnaWVzIHRoYXQgd291bGQgaGVscCBDeWNsaXN0aWMgbWF4aW1pemVkIHRoZSBudW1iZXIgb2YgYW5udWFsIG1lbWJlcnNoaXBzLnRhY3RpY3MuIE1vcmVubyBhbmQgaGVyIHRlYW0gYXJlIGludGVyZXN0ZWQgaW4gYW5hbHl6aW5nIHRoZSBDeWNsaXN0aWMgaGlzdG9yaWNhbCBiaWtlIHRyaXAgZGF0YSB0byBpZGVudGlmeSB0cmVuZHMuIFlvdXIgdGVhbSBpcyB0YXNrZWQgdG8gcHJlc2VudCBkYXRhIHdpdGggc2ltcGxlIGFuZCBjb21wZWxsaW5nIHZpc3VhbGl6YXRpb25zIHRoYXQgd2lsbCBjb21tdW5pY2F0ZSBpbnNpZ2h0cyBmb3IgQ3ljbGlzdGljIGV4ZWN1dGl2ZXMnZGVjaXNpb24gbWFraW5nIHByb2Nlc3MuDQoNCiMjIyMgVGhpcyBhbmFseXNpcyBpcyBmb3IgY2FzZSBzdHVkeSAxIGZyb20gdGhlIEdvb2dsZSBEYXRhIEFuYWx5dGljcyBDZXJ0aWZpY2F0ZSAoQ3ljbGlzdGljKS4gIEl04oCZcyBvcmlnaW5hbGx5IGJhc2VkIG9uIHRoZSBjYXNlIHN0dWR5ICInU29waGlzdGljYXRlZCwgQ2xlYXIsIGFuZCBQb2xpc2hlZOKAmTogRGl2dnkgYW5kIERhdGEgVmlzdWFsaXphdGlvbiIgd3JpdHRlbiBieSAqKktldmluIEhhcnRtYW4qKiAoZm91bmQgaGVyZTogaHR0cHM6Ly9hcnRzY2llbmNlLmJsb2cvaG9tZS9kaXZ2eS1kYXRhdml6LWNhc2Utc3R1ZHkpLg0KIA0KIyMjIyAgV2Ugd2lsbCBmb2xsb3cgdGhlIGRhdGEgYW5hbHlzaXMgcHJvY2VzczogYXNrLCBwcmVwYXJlLCBwcm9jZXNzLCBhbmFseXplLHNoYXJlLCBhbmQgYWN0IHRvIGNvbWUgdXAgd2l0aCBhIHNvbHV0aW9uIG9yIG1ha2UgcmVjY29tZW5kYXRpb25zIGZvciB0aGUgYnVzaW5lc3MgdGFzaw0KDQojIyMgKipCdXNpbmVzcyBUYXNrOioqIEhvdyBjYW4gQ3ljbGlzdGljIG1heGltaXplICB0aGUgbnVtYmVyIG9mIGFubnVhbCBtZW1iZXJzaGlwcz8NCg0KIyMgU1RFUCAxOiBDT0xMRUNUIERBVEENCiMjIyMgV2UgYmVnaW4gdGhlIGFuYWx5c2lzIGJ5IGxvYWRpbmcgdGhlIFIgcGFja2FnZXMgbmVlZGVkIGZvciB0aGlzIGV4ZXJjaXNlDQpgYGB7ciBJbnN0YWxsaW5nIHBhY2thZ2VzfQ0KaW5zdGFsbC5wYWNrYWdlcygidGlkeXZlcnNlIikNCmluc3RhbGwucGFja2FnZXMoImx1YnJpZGF0ZSIpDQppbnN0YWxsLnBhY2thZ2VzKCJkcGx5ciIpDQppbnN0YWxsLnBhY2thZ2VzKCJnZ3Bsb3QyIikNCmluc3RhbGwucGFja2FnZXMoImdndGhlbWVzIikNCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShsdWJyaWRhdGUpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShnZ3RoZW1lcykNCmdldHdkKCkNCnNldHdkKCIvVXNlcnMvdHVzaHkvT25lRHJpdmUvRGVza3RvcC9DYXBzdG9uZSBHb29nbGUvQ2Fwc3RvbmUiKQ0KYGBgDQojIyMgU1RFUCAxOiBDT0xMRUNUIERBVEENCiMjIyMgTG9hZGluZyBkYXRhICgxMiBtb250aHMgU3RhcnRpbmcgT2N0b2JlciAyMDIwIHRvIGVuZCBvZiBTZXB0ZW1iZXIgMjAyMSBmb3IgdGhpcyBhbmFseXNpcykgDQpgYGB7ciBMb2FkaW5nIGRhdGEgLSBkb3dubG9hZGVkIGFuZCB1c2VkIHBseXIgYmVmb3JlIGxpbmtpbmcgZGF0YX0NCmJpa2V0cmlwZGF0YV8yMDIwXzEwIDwtIHJlYWQuY3N2KCIyMDIwMTAtZGl2dnktdHJpcGRhdGEuY3N2IikNCmJpa2V0cmlwZGF0YV8yMDIwXzExIDwtIHJlYWQuY3N2KCIyMDIwMTEtZGl2dnktdHJpcGRhdGEuY3N2IikNCmJpa2V0cmlwZGF0YV8yMDIwXzEyIDwtIHJlYWQuY3N2KCIyMDIwMTItZGl2dnktdHJpcGRhdGEuY3N2IikNCmJpa2V0cmlwZGF0YV8yMDIxXzAxIDwtIHJlYWQuY3N2KCIyMDIxMDEtZGl2dnktdHJpcGRhdGEuY3N2IikNCmJpa2V0cmlwZGF0YV8yMDIxXzAyIDwtIHJlYWQuY3N2KCIyMDIxMDItZGl2dnktdHJpcGRhdGEuY3N2IikNCmJpa2V0cmlwZGF0YV8yMDIxXzAzIDwtIHJlYWQuY3N2KCIyMDIxMDMtZGl2dnktdHJpcGRhdGEuY3N2IikNCmJpa2V0cmlwZGF0YV8yMDIxXzA0IDwtIHJlYWQuY3N2KCIyMDIxMDQtZGl2dnktdHJpcGRhdGEuY3N2IikNCmJpa2V0cmlwZGF0YV8yMDIxXzA1IDwtIHJlYWQuY3N2KCIyMDIxMDUtZGl2dnktdHJpcGRhdGEuY3N2IikNCmJpa2V0cmlwZGF0YV8yMDIxXzA2IDwtIHJlYWQuY3N2KCIyMDIxMDYtZGl2dnktdHJpcGRhdGEuY3N2IikNCmJpa2V0cmlwZGF0YV8yMDIxXzA3IDwtIHJlYWQuY3N2KCIyMDIxMDctZGl2dnktdHJpcGRhdGEuY3N2IikNCmJpa2V0cmlwZGF0YV8yMDIxXzA4IDwtIHJlYWQuY3N2KCIyMDIxMDgtZGl2dnktdHJpcGRhdGEuY3N2IikNCmJpa2V0cmlwZGF0YV8yMDIxXzA5IDwtIHJlYWQuY3N2KCIyMDIxMDktZGl2dnktdHJpcGRhdGEuY3N2IikNCmBgYA0KDQojIyMgU1RFUCAyOiBXUkFOR0xFIERBVEEgQU5EIENPTUJJTkUgSU5UTyBBIFNJTkdMRSBGSUxFDQpgYGB7ciB1c2VkIENvbG5hbWVzKCkgZnVuY3Rpb24sIGluY2x1ZGU9RkFMU0V9DQpjb2xuYW1lcyhiaWtldHJpcGRhdGFfMjAyMF8xMCkNCmNvbG5hbWVzKGJpa2V0cmlwZGF0YV8yMDIwXzExKQ0KY29sbmFtZXMoYmlrZXRyaXBkYXRhXzIwMjBfMTIpDQpjb2xuYW1lcyhiaWtldHJpcGRhdGFfMjAyMV8wMSkNCmNvbG5hbWVzKGJpa2V0cmlwZGF0YV8yMDIxXzAyKQ0KY29sbmFtZXMoYmlrZXRyaXBkYXRhXzIwMjFfMDMpDQpjb2xuYW1lcyhiaWtldHJpcGRhdGFfMjAyMV8wNCkNCmNvbG5hbWVzKGJpa2V0cmlwZGF0YV8yMDIxXzA1KQ0KY29sbmFtZXMoYmlrZXRyaXBkYXRhXzIwMjFfMDYpDQpjb2xuYW1lcyhiaWtldHJpcGRhdGFfMjAyMV8wNykNCmNvbG5hbWVzKGJpa2V0cmlwZGF0YV8yMDIxXzA4KQ0KY29sbmFtZXMoYmlrZXRyaXBkYXRhXzIwMjFfMDkpDQpgYGANCiMjIyMgTG9va2luZyBhdCBkYXRhIGRldGFpbHMgbW9yZSBjbG9zZWx5IHVzaW5nIHN0cigpDQpgYGB7ciBVc2VkIHN0ciB0byBpbnNwZWN0IGRhdGEsIGluY2x1ZGU9RkFMU0V9DQpzdHIoYmlrZXRyaXBkYXRhXzIwMjBfMTApDQpzdHIoYmlrZXRyaXBkYXRhXzIwMjBfMTEpDQpzdHIoYmlrZXRyaXBkYXRhXzIwMjBfMTIpDQpzdHIoYmlrZXRyaXBkYXRhXzIwMjFfMDEpDQpzdHIoYmlrZXRyaXBkYXRhXzIwMjFfMDIpDQpzdHIoYmlrZXRyaXBkYXRhXzIwMjFfMDMpDQpzdHIoYmlrZXRyaXBkYXRhXzIwMjFfMDQpDQpzdHIoYmlrZXRyaXBkYXRhXzIwMjFfMDUpDQpzdHIoYmlrZXRyaXBkYXRhXzIwMjFfMDYpDQpzdHIoYmlrZXRyaXBkYXRhXzIwMjFfMDcpDQpzdHIoYmlrZXRyaXBkYXRhXzIwMjFfMDgpDQpzdHIoYmlrZXRyaXBkYXRhXzIwMjFfMDkpDQoNCmBgYA0KIyMjIyBXZSBuZWVkIHRoZSBhbGwgZGF0YSB0byBzdGFjayBjb3JyZWN0bHkgd2hlbiB3ZSBjb21iaW5lIHRoZSBhbGwgdGhlIGRhdGEgZm9yIGFuYWx5c2lzLiBTb21lIG9mIHRoZSBzdGFydF9zdGF0aW9uX2lkJ3MgYW5kIGVuZF9zdGF0aW9uX2lkJ3MgbmVlZCB0byBiZSBjb252ZXJ0ZWQgaW50byBjaGFyYWN0ZXJzIGZvcm1hdCBmb3IgdGhpcyBwcm9jZXNzLg0KDQpgYGB7ciB1c2luZyBtdXRhdGUgdG8gZW5hYmxlIHByb3BlciBzdGFja2luZyBiZWZvcmUgam9pbmluZ30NCmJpa2V0cmlwZGF0YV8yMDIwXzEwIDwtIG11dGF0ZShiaWtldHJpcGRhdGFfMjAyMF8xMCwgc3RhcnRfc3RhdGlvbl9pZCA9IGFzLmNoYXJhY3RlcihzdGFydF9zdGF0aW9uX2lkKSwgZW5kX3N0YXRpb25faWQgPSBhcy5jaGFyYWN0ZXIoZW5kX3N0YXRpb25faWQpKQ0KYmlrZXRyaXBkYXRhXzIwMjBfMTEgPC0gbXV0YXRlKGJpa2V0cmlwZGF0YV8yMDIwXzExLCBzdGFydF9zdGF0aW9uX2lkID0gYXMuY2hhcmFjdGVyKHN0YXJ0X3N0YXRpb25faWQpLCBlbmRfc3RhdGlvbl9pZCA9IGFzLmNoYXJhY3RlcihlbmRfc3RhdGlvbl9pZCkpDQpgYGANCiMjIyMgQWZ0ZXIgdGhlIGRhdGEgaXMgc3RhY2tlZCBjb3JyZWN0bHkgd2UgYmluZCBhbGwgb2Ygb3VyIGNzdiBzcHJlYWRzaGVldHMgZm9yIGFuYWx5c2lzIA0KDQpgYGB7ciBiaW5kaW5nICBhbGwgcm93cyB0byBwdXQgYWxsIGRhdGEgaW4gb25lIGRvY3VtZW50fQ0KdG90X2Jpa2V0cmlwcyA8LWJpbmRfcm93cygNCmJpa2V0cmlwZGF0YV8yMDIwXzEwLA0KYmlrZXRyaXBkYXRhXzIwMjBfMTEsDQpiaWtldHJpcGRhdGFfMjAyMF8xMiwNCmJpa2V0cmlwZGF0YV8yMDIxXzAxLA0KYmlrZXRyaXBkYXRhXzIwMjFfMDIsDQpiaWtldHJpcGRhdGFfMjAyMV8wMywNCmJpa2V0cmlwZGF0YV8yMDIxXzA0LA0KYmlrZXRyaXBkYXRhXzIwMjFfMDUsDQpiaWtldHJpcGRhdGFfMjAyMV8wNiwNCmJpa2V0cmlwZGF0YV8yMDIxXzA3LA0KYmlrZXRyaXBkYXRhXzIwMjFfMDgsDQpiaWtldHJpcGRhdGFfMjAyMV8wOSkNCmBgYA0KIyMjIyBJbnNwZWN0IGRhdGEgdG8gdmlldyBjaGFuZ2VzIGFuZCBDbGVhbiBiZWZvcmUgYW5hbHlzaXMgdXNpbmcgdGhlc2UgZnVuY3Rpb25zOiBjb2xuYW1lcygpL3N1bW1hcnkoKS9oZWFkKCkNCmBgYHtyIGluc3BlY3Rpb24gYWZ0ZXIgam9pbmluZywgZWNobz1UUlVFfQ0KY29sbmFtZXModG90X2Jpa2V0cmlwcykNCnN1bW1hcnkodG90X2Jpa2V0cmlwcykNCmhlYWQodG90X2Jpa2V0cmlwcykNCmBgYA0KIyMgU1RFUCAzOiBDTEVBTiBVUCBBTkQgQUREIERBVEEgVE8gUFJFUEFSRSBGT1IgQU5BTFlTSVMNCg0KIyMjIyBJbiBvcmRlciB0byBkbyBmdXJ0aGVyIGNsZWFuaW5nIGFuZCBjYWxjdWxhdGlvbnMsIHdlIG11c3QgYWRkIGRhdGVzIG9mIGNvbHVtbnMgdG8gdGhlIGNsZWFuZWQgZGF0YSBzZXQgc3VjaCBhcyBkYXksIHdlZWssIG1vbnRoIGFuZCB5ZWFyLg0KYGBge3J9DQp0b3RfYmlrZXRyaXBzX2NsZWFuJGRhdGUgPC0gYXMuRGF0ZSh0b3RfYmlrZXRyaXBzX2NsZWFuJHN0YXJ0ZWRfYXQpDQp0b3RfYmlrZXRyaXBzX2NsZWFuJG1vbnRoIDwtIGZvcm1hdChhcy5EYXRlKHRvdF9iaWtldHJpcHNfY2xlYW4kZGF0ZSksICIlbSIpDQp0b3RfYmlrZXRyaXBzX2NsZWFuJGRheSA8LSBmb3JtYXQoYXMuRGF0ZSh0b3RfYmlrZXRyaXBzX2NsZWFuJGRhdGUpLCAiJWQiKQ0KdG90X2Jpa2V0cmlwc19jbGVhbiR5ZWFyIDwtIGZvcm1hdChhcy5EYXRlKHRvdF9iaWtldHJpcHNfY2xlYW4kZGF0ZSksICIlWSIpDQp0b3RfYmlrZXRyaXBzX2NsZWFuJGRheV9vZl93ZWVrIDwtIGZvcm1hdChhcy5EYXRlKHRvdF9iaWtldHJpcHNfY2xlYW4kZGF0ZSksICIlQSIpDQpgYGANCiMjIyBjaGVjayBvdXQgcm93cyB0byBzZWUgaWYgdGhlIGRhdGUgY29sdW1ucyB3ZXJlIGFkZGVkDQpgYGB7ciB1c2luZyBHbGltcHNlIGFuZCBzdW1tYXJ5IHRvIHZpZXcgYWRkaXRpb25zIHRvIGRhdGEgc2V0IH0NCmdsaW1wc2UodG90X2Jpa2V0cmlwc19jbGVhbikNCnN1bW1hcnkodG90X2Jpa2V0cmlwc19jbGVhbikNCmBgYA0KIyMjIEFkZCBhICJyaWRlX2xlbmd0aCIgY2FsY3VsYXRpb24gdG8gdG90X2Jpa2V0cmlwcyBjbGVhbmVkICAoaW4gc2Vjb25kcykNCmBgYHtyIHVzaW5nIGRpZmZ0aW1lKCkgfQ0KdG90X2Jpa2V0cmlwc19jbGVhbiRyaWRlX2xlbmd0aCA8LSBkaWZmdGltZSh0b3RfYmlrZXRyaXBzX2NsZWFuJGVuZGVkX2F0LHRvdF9iaWtldHJpcHNfY2xlYW4kc3RhcnRlZF9hdCkNCmBgYA0KIyMjMyBDaGVja2luZyBkYXRhIHR5cGUgZm9yIGNvbnNpc3RlbmN5DQpgYGB7ciBkYXRhIGNoZWNrIGZvciBjb25zaXN0ZW5jeSwgaW5jbHVkZT1GQUxTRX0NCmlzLmZhY3Rvcih0b3RfYmlrZXRyaXBzX2NsZWFuJHJpZGVfbGVuZ3RoKQ0KdG90X2Jpa2V0cmlwc19jbGVhbiRyaWRlX2xlbmd0aCA8LSBhcy5udW1lcmljKGFzLmNoYXJhY3Rlcih0b3RfYmlrZXRyaXBzX2NsZWFuJHJpZGVfbGVuZ3RoKSkNCmlzLm51bWVyaWModG90X2Jpa2V0cmlwc19jbGVhbiRyaWRlX2xlbmd0aCkNCmBgYA0KIyMjIFJlbW92ZSAiYmFkIiBkYXRhIGZvciBiaWtlcyB0YWtlbiBvdXQgb2YgY2lyY3VsYXRpb24gaW4gb3JkZXIgdG8gb21pdCBuZWdhdGl2ZSB2YWx1ZXMgaW4gcmlkZSBsZW5ndGggDQpgYGB7cn0NCnRvdF9iaWtldHJpcHNfdjIgPC0gdG90X2Jpa2V0cmlwc19jbGVhblshKHRvdF9iaWtldHJpcHNfY2xlYW4kc3RhcnRfc3RhdGlvbl9uYW1lID09ICJIUSBRUiIgfCB0b3RfYmlrZXRyaXBzX2NsZWFuJHJpZGVfbGVuZ3RoPDApLF0NCg0KYGBgDQojIyBTVEVQIDQ6IENPTkRVQ1QgREVTQ1JJUFRJVkUgQU5BTFlTSVMNCmBgYHtyfQ0KbWluKHRvdF9iaWtldHJpcHNfdjIkcmlkZV9sZW5ndGgpICMgc2hvcnRlc3QgcmlkZQ0KbWF4KHRvdF9iaWtldHJpcHNfdjIkcmlkZV9sZW5ndGgpICMgbG9uZ2VzdCByaWRlDQptZWFuKHRvdF9iaWtldHJpcHNfdjIkcmlkZV9sZW5ndGgpICMgYXZlcmFnZSAodG90YWwgcmlkZSBsZW5ndGggLyByaWRlcykNCm1lZGlhbih0b3RfYmlrZXRyaXBzX3YyJHJpZGVfbGVuZ3RoKSAjIG1pZHBvaW50DQpzdW1tYXJ5KHRvdF9iaWtldHJpcHNfdjIpDQoNCmBgYA0KIyMjIENvbXBhcmUgbWVtYmVycyBhbmQgY2FzdWFsIHVzZXJzDQpgYGB7ciBDYWxjdWxhdGluZzogbWVhbixtZWRpYW4sbWF4aW11bSBhbmQgbWluaW11bSB9DQphZ2dyZWdhdGUodG90X2Jpa2V0cmlwc192MiRyaWRlX2xlbmd0aCB+IHRvdF9iaWtldHJpcHNfdjIkbWVtYmVyX2Nhc3VhbCwgRlVOID0gbWVhbikNCmFnZ3JlZ2F0ZSh0b3RfYmlrZXRyaXBzX3YyJHJpZGVfbGVuZ3RoIH4gdG90X2Jpa2V0cmlwc192MiRtZW1iZXJfY2FzdWFsLCBGVU4gPSBtZWRpYW4pDQphZ2dyZWdhdGUodG90X2Jpa2V0cmlwc192MiRyaWRlX2xlbmd0aCB+IHRvdF9iaWtldHJpcHNfdjIkbWVtYmVyX2Nhc3VhbCwgRlVOID0gbWF4KQ0KYWdncmVnYXRlKHRvdF9iaWtldHJpcHNfdjIkcmlkZV9sZW5ndGggfiB0b3RfYmlrZXRyaXBzX3YyJG1lbWJlcl9jYXN1YWwsIEZVTiA9IG1pbikNCg0KYGBgDQojIyMgU2VlIHRoZSBhdmVyYWdlIHJpZGUgdGltZSBieSBlYWNoIGRheSBmb3IgbWVtYmVycyB2cyBjYXN1YWwgdXNlcnMNCmBgYHtyfQ0KYWdncmVnYXRlKHRvdF9iaWtldHJpcHNfdjIkcmlkZV9sZW5ndGggfiB0b3RfYmlrZXRyaXBzX3YyJG1lbWJlcl9jYXN1YWwgKyB0b3RfYmlrZXRyaXBzX3YyJGRheV9vZl93ZWVrLCBGVU4gPSBtZWFuKQ0KYGBgDQojIyMgT3JnYW5pemUgdGhlIHdlZWsgZGF5cyANCmBgYHtyfQ0KdG90X2Jpa2V0cmlwc192MiRkYXlfb2Zfd2VlayA8LSBvcmRlcmVkKHRvdF9iaWtldHJpcHNfdjIkZGF5X29mX3dlZWssIGxldmVscz1jKCJTdW5kYXkiLCAiTW9uZGF5IiwgIlR1ZXNkYXkiLCAiV2VkbmVzZGF5IiwgIlRodXJzZGF5IiwgIkZyaWRheSIsICJTYXR1cmRheSIpKQ0KYGBgDQojIyMgRGV0ZW1pbmUgdGhlIGF2ZXJhZ2UgcmlkZSB0aW1lIGJ5IGVhY2ggZGF5IGZvciBtZW1iZXJzIHZzIGNhc3VhbCB1c2Vycw0KYGBge3J9DQphZ2dyZWdhdGUodG90X2Jpa2V0cmlwc192MiRyaWRlX2xlbmd0aCB+IHRvdF9iaWtldHJpcHNfdjIkbWVtYmVyX2Nhc3VhbCArIHRvdF9iaWtldHJpcHNfdjIkZGF5X29mX3dlZWssIEZVTiA9IG1lYW4pDQpgYGANCiMjIyBBbmFseXplIHJpZGVyc2hpcCBkYXRhIGJ5IHR5cGUgYW5kIHdlZWtkYXkNCmANCmBgYHtyfQ0KdG90X2Jpa2V0cmlwc192MiAlPiUNCm11dGF0ZSh3ZWVrZGF5ID0gd2RheShzdGFydGVkX2F0LCBsYWJlbCA9IFRSVUUpKSAlPiUgICNjcmVhdGVzIHdlZWtkYXkgZmllbGQgdXNpbmcgd2RheSgpDQpncm91cF9ieShtZW1iZXJfY2FzdWFsLCB3ZWVrZGF5KSAlPiUgICNncm91cHMgYnkgdXNlcnR5cGUgYW5kIHdlZWtkYXkNCnN1bW1hcmlzZShudW1iZXJfb2ZfcmlkZXMgPSBuKCkJCQkJCQkJI2NhbGN1bGF0ZXMgdGhlIG51bWJlciBvZiByaWRlcyBhbmQgYXZlcmFnZSBkdXJhdGlvbg0KLGF2ZXJhZ2VfZHVyYXRpb24gPSBtZWFuKHJpZGVfbGVuZ3RoKSkgJT4lIAkJIyBjYWxjdWxhdGVzIHRoZSBhdmVyYWdlIGR1cmF0aW9uDQphcnJhbmdlKG1lbWJlcl9jYXN1YWwsIHdlZWtkYXkpCQkJCQkJCQkjIHNvcnRzDQpWaWV3KHRvdF9iaWtldHJpcHNfdjIpDQpgYGANCiMjIFNURVAgNTogVklTVUFMSVpBVElPTiBTRUNUSU9OIA0KDQojIyMgVmlzdWFsaXplIHRoZSBudW1iZXIgb2YgcmlkZXMgYnkgcmlkZXIgdHlwZQ0KYGBge3IgZ3JhcGggIyBvZiByaWRlcyBieSByaWRlciB0eXBlfQ0KdG90X2Jpa2V0cmlwc192MiAlPiUgDQogIG11dGF0ZSh3ZWVrZGF5ID0gd2RheShzdGFydGVkX2F0LCBsYWJlbCA9IFRSVUUpKSAlPiUgDQogIGdyb3VwX2J5KG1lbWJlcl9jYXN1YWwsIHdlZWtkYXkpICU+JSANCiAgc3VtbWFyaXNlKG51bWJlcl9vZl9yaWRlcyA9IG4oKQ0KICAgICAgICAgICAgLGF2ZXJhZ2VfZHVyYXRpb24gPSBtZWFuKHJpZGVfbGVuZ3RoKSkgJT4lIA0KICBhcnJhbmdlKG1lbWJlcl9jYXN1YWwsIHdlZWtkYXkpICAlPiUgDQogIGdncGxvdChhZXMoeCA9IHdlZWtkYXksIHkgPSBudW1iZXJfb2ZfcmlkZXMsIGZpbGwgPSBtZW1iZXJfY2FzdWFsLCBjYXB0aW9uID0gIkRhdGEgYnkgTW90aXZhdGUgSW50ZXJuYXRpb25hbCBJbmMiKSkgKw0KICBnZW9tX2NvbChwb3NpdGlvbiA9ICJkb2RnZSIpKw0KICB0aGVtZV9idygpDQogIA0KDQpgYGANCiMjIyBWaXN1YWxpemF0aW9uIGZvciBhdmVyYWdlIGR1cmF0aW9uIG9mIHJpZGVzDQpgYGB7ciBncmFwaCBkdXJhdGlvbiBvZiByaWRlcywgZWNobz1GQUxTRX0NCnRvdF9iaWtldHJpcHNfdjIgJT4lDQptdXRhdGUod2Vla2RheSA9IHdkYXkoc3RhcnRlZF9hdCwgbGFiZWwgPSBUUlVFKSkgJT4lDQpncm91cF9ieShtZW1iZXJfY2FzdWFsLCB3ZWVrZGF5KSAlPiUNCnN1bW1hcmlzZShudW1iZXJfb2ZfcmlkZXMgPSBuKCkNCixhdmVyYWdlX2R1cmF0aW9uID0gbWVhbihyaWRlX2xlbmd0aCkpICU+JQ0KYXJyYW5nZShtZW1iZXJfY2FzdWFsLCB3ZWVrZGF5KSAgJT4lDQpnZ3Bsb3QoYWVzKHggPSB3ZWVrZGF5LCB5ID0gYXZlcmFnZV9kdXJhdGlvbiwgZmlsbCA9IG1lbWJlcl9jYXN1YWwpKSArDQpnZW9tX2NvbChwb3NpdGlvbiA9ICJkb2RnZSIpKw0KdGhlbWVfYncoKQ0KDQpgYGANCiMjIyBNYXRjaGluZyByaWRlciB3aXRoIHR5cGUgb2YgYmlrZQ0KYGBge3IgZ3JhcGggbWF0Y2hpbmcgYmlrZSB0eXBlICB3aXRoIHJpZGVyfQ0KICB0eXBlX29mX2Jpa2UgPC0gdG90X2Jpa2V0cmlwc19jbGVhbiAlPiUgZmlsdGVyKHJpZGVhYmxlX3R5cGU9PSJjbGFzc2ljX2Jpa2UiIHwgcmlkZWFibGVfdHlwZT09ImVsZWN0cmljX2Jpa2UiKQ0KYGBgDQoNCiMjIyBjaGVja2luZyB0aGUgYmlrZSB0eXBlIHVzYWdlIGJ5IHVzZXIgdHlwZQ0KYGBge3IgZ3JhcGgtIGNoZWNraW5nIHVzYWdlIGJ5IHVzZXJ9DQogIHR5cGVfb2ZfYmlrZSAlPiUNCmdyb3VwX2J5KG1lbWJlcl9jYXN1YWwscmlkZWFibGVfdHlwZSkgJT4lDQogICAgc3VtbWFyaXNlKHRvdGFscz1uKCksIC5ncm91cHM9ImRyb3AiKSAgJT4lDQoNCmdncGxvdCgpKw0KICAgIGdlb21fY29sKGFlcyh4PW1lbWJlcl9jYXN1YWwseT10b3RhbHMsZmlsbD1yaWRlYWJsZV90eXBlKSwgcG9zaXRpb24gPSAiZG9kZ2UiKSArIA0KICAgIGxhYnModGl0bGUgPSAiVHlwZSBvZiBiaWtlIHVzYWdlIGJ5IFJpZGVyIHR5cGUiLHg9IlJpZGVyIHR5cGUiLHk9TlVMTCwgZmlsbD0iQmlrZSB0eXBlIikgKw0KICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoImNsYXNzaWNfYmlrZSIgPSAiIzcyOTFDOSIsImVsZWN0cmljX2Jpa2UiID0gIiM3OUNDODUiKSkgKw0KICAgIHRoZW1lX2J3KCkgKw0KICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0idG9wIikNCg0KYGBgDQojIyMgQmlrZSB1c2FnZSBieSBib3RoIHVzZXIgdHlwZXMgZHVyaW5nIGEgd2Vlaw0KYGBge3IgZ3JhcGggZGFpbHkgcmlkZXJzaGlwfQ0KICB0eXBlX29mX2Jpa2UgJT4lDQogICAgbXV0YXRlKHdlZWtkYXkgPSB3ZGF5KHN0YXJ0ZWRfYXQsIGxhYmVsID0gVFJVRSkpICU+JSANCiAgICBncm91cF9ieShtZW1iZXJfY2FzdWFsLHJpZGVhYmxlX3R5cGUsd2Vla2RheSkgJT4lDQogICAgc3VtbWFyaXNlKHRvdGFscz1uKCksIC5ncm91cHM9ImRyb3AiKSAlPiUNCg0KZ2dwbG90KGFlcyh4PXdlZWtkYXkseT10b3RhbHMsIGZpbGw9cmlkZWFibGVfdHlwZSkpICsNCiAgZ2VvbV9jb2wocG9zaXRpb24gPSAiZG9kZ2UiKSArIA0KICBmYWNldF93cmFwKH5tZW1iZXJfY2FzdWFsKSArDQogIGxhYnModGl0bGUgPSAiQmlrZSB0eXBlIHVzYWdlIGJ5IHVzZXIgdHlwZSBkdXJpbmcgYSB3ZWVrIix4PSJVc2VyIHR5cGUiLHk9TlVMTCxjYXB0aW9uID0gIkRhdGEgYnkgTW90aXZhdGUgSW50ZXJuYXRpb25hbCBJbmMiKSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoImNsYXNzaWNfYmlrZSIgPSAiIzcyOTFDOSIsImVsZWN0cmljX2Jpa2UiID0gIiM3OUNDODUiKSkgKw0KICB0aGVtZV9idygpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJ0b3AiKQ0KDQoNCg0KYGBgDQoNCg0K